feat(autonumber): date, {field} & per-scope counter reset for autonumber formats#2043
Merged
Conversation
Tokenize autonumberFormat via a shared pure renderer in @objectstack/spec (parseAutonumberFormat / renderAutonumber) that both the engine fallback and the SQL driver call, so they emit byte-identical numbers (#1603 parity): - date tokens {YYYY}{YY}{MM}{DD}{YYYYMMDD} resolve the calendar day in the request's business timezone (ExecutionContext.timezone, ADR-0053; UTC fallback), threaded through new DriverOptions.timezone - {field} interpolation substitutes record values into the prefix - counter scope = rendered prefix before the sequence slot, so AD{YYYYMMDD}{0000} resets daily, {section}{island_zone}{000} numbers per group, {plan_no}{000} numbers per parent — one mechanism, no separate reset config Fixed-prefix formats (CASE-{0000}) render an empty scope and keep their single global counter. _objectstack_sequences gains a scope column (PK widened to object,tenant_id,field,scope); legacy 3-column tables migrate in place on first use, carrying existing counters to scope=''.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 98 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The empty-prefix legacy branch used /(\d+)(?!.*\d)/ to grab the last digit run, whose negative lookahead is a polynomial-ReDoS sink on stored values with many repeated zeros (CodeQL js/polynomial-redos, high). Replace both branches with the linear /\d+/g, preserving the last-digit-run semantics.
Add three guardrails on top of the {field}/date/per-scope autonumber work:
- Empty interpolated {field} now throws (shared missingFieldValues helper)
in both the SQL driver and the engine fallback, instead of silently
collapsing the record into the wrong counter scope.
- Build-time lint (objectstack compile): unknown / self-referencing {field}
fails the build; an optional {field} warns to mark it required.
- Legacy _objectstack_sequences PK-widen failure fails safe — fixed-prefix
sequences keep working and a per-scope write raises an actionable error
rather than an opaque DB primary-key violation at insert time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…antics Full hardening of the remaining items from review: - #5 MySQL/length: key _objectstack_sequences by a single key_hash (SHA-256 of object,tenant_id,field,scope) instead of a 4-column natural PK. The natural PK exceeded MySQL's utf8mb4 index-length limit (a certain CREATE TABLE failure) and bounded how long a {field} scope could be. The hash PK keys every dialect uniformly and lets scope be a generous non-indexed column. Legacy 3-column and interim {scope}-column tables are migrated in place; migration fails safe (fixed-prefix keeps working, a per-scope write errors actionably). - #1 scope ambiguity: confirmed NOT fixable by separating adjacent token boundaries — when two records render the same prefix they render the same visible number, so they MUST share a counter to stay unique (a separator would mint duplicates). Documented the semantics + the remedy (delimiter literal in the format), backed by tests. The compile lint already nudges authors toward unambiguous formats. - #6 width overflow: confirmed by-design — the pad width is a MINIMUM, the counter grows past it and never wraps (mainstream autonumber semantics). Documented + regression test, no throw (throwing would break legitimate high-count sequences). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
评审后加固总结(合并前补充的两个提交)在原方案(date /
|
| 项 | 问题 | 处理 |
|---|---|---|
| #2 空字段静默错号 | 被插值的 {field} 在生成时为空 → 渲染成空前缀,记录被并进错误的计数器 scope(最易发生) |
新增共享 helper missingFieldValues();SQL driver 与 engine fallback 两条路径生成前检查,为空即抛出指名字段的清晰错误(保持 #1603 parity) |
| #3 无建模期校验 | {field} 引用不存在/可选字段,一路绿灯到运行期才暴露 |
新增 lint-autonumber-formats.ts 接入 objectstack compile:引用不存在字段/自引用 → 构建失败;引用可选字段 → 警告提示标 required: true(对齐 broken→error / fragile→warning 两级护栏) |
| #4 迁移失败潜伏 | 旧序列表 PK 加宽 best-effort 失败只 warn → 后续 per-scope 写入撞不透明 PK 违反 | 失败即安全:固定前缀照常工作,per-scope 写入抛可操作错误(此项在 9813c420 随表结构重设计进一步收敛) |
9813c420 — 序列表重设计 + 语义澄清
| 项 | 结论 | 处理 |
|---|---|---|
| #5 MySQL/长度 | 四列自然主键 (object,tenant_id,field,scope) 在 MySQL utf8mb4 下 = 4080 字节 > 3072 索引上限 → CREATE TABLE 必现失败(非边缘),且限制 {field} scope 长度 |
_objectstack_sequences 改为 key_hash(SHA-256 of object,tenant_id,field,scope)单列主键:各方言统一、远低于索引上限、scope 变非索引 varchar(1024);旧 3 列表 / 中间态 scope 列表均就地迁移,迁移失败即安全降级 |
| #1 拼接歧义 | 调查后纠正判断:('AB','C') 与 ('A','BC') 渲染出相同前缀 ABC ⇒ 可见编号本就相同,必须共用计数器才能保唯一;加分隔符独立计数反而会产出重复编号。原 scope = prefix 是对的 |
回退分隔符改动;语义写进文档(真正解法=格式加分隔字面量,由 #3 lint 引导),测试锁定 |
| #6 宽度溢出 | 核实为 by-design:pad 宽度是最小宽度,超出自然增宽({000}→1000)、不环绕,与 Salesforce/Dynamics 一致;报错会破坏合法高计数序列 |
不抛错;文档 + 回归测试锁定 |
改动文件
packages/spec/src/data/autonumber-format.ts—missingFieldValues();scope 语义注释packages/spec/src/data/field.zod.ts— 文档(字段须 required/已设;宽度为最小值)packages/objectql/src/engine.ts— fallback 空字段抛错packages/plugins/driver-sql/src/sql-driver.ts—key_hash表结构 + 迁移 + 空字段抛错 + 失败即安全packages/cli/src/utils/lint-autonumber-formats.ts(新)+commands/compile.ts— 建模期 lint
验证
spec autonumber 16/16 · driver-sql 全量 180/180 · objectql 670/670 · cli 490/490;四包 tsc/构建通过;CI 全绿后合并。
os-zhuang
added a commit
that referenced
this pull request
Jun 19, 2026
…rmat tokens (#2046) Follow-up to #2043 — close the AI-authoring gap on top of the runtime guards: - skills/objectstack-data field-types rules: the autonumber section only showed `CASE-{0000}`. Expanded with the date / {field} / per-scope tokens and the authoring rules that prevent silent mis-numbering — interpolated fields must be required, put a delimiter between adjacent variable tokens, pad width is a minimum, date tokens are exact/case-sensitive — plus an incorrect/correct example. - compile lint: warn when an autonumber `{...}` token is not a counter/date/{field} token. Unrecognized groups (wrong case, spaces, a second {0..0} slot) render LITERALLY into the record number; the field-reference checks miss the non-identifier cases. New advisory rule `autonumber-unrecognized-token`. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
pushed a commit
that referenced
this pull request
Jun 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
autonumberFormat此前只认一个{0000}序列槽 + 固定字面前缀 + 单一全局计数器,无法表达真实 MES/eHR 的编码规则(按日期重置、把记录字段拼进前缀、按父计划/分组独立编号)。方案
把格式 tokenize 进一个放在
@objectstack/spec的纯函数渲染器(parseAutonumberFormat/renderAutonumber),engine 内存兜底与 SQL driver 共用它,保证两条路径产出逐字节一致(#1603 parity)。四类 token:
AD{YYYYMMDD}{0000}execCtx.timezone取日历日(ADR-0053,UTC 兜底),每日重置{section}{island_zone}{000}{plan_no}{000}CASE-{0000}核心设计:计数器 scope = 序列槽之前渲染出的前缀。于是「每日重置 / 每组编号 / 每父编号」全部由同一机制自然得出,无需额外 reset 配置。
改动
packages/spec/src/data/autonumber-format.ts— 共享渲染器driver.zod.ts新增DriverOptions.timezone;engine.ts经buildDriverOptions从execCtx.timezone注入sql-driver.ts—_objectstack_sequences加scope列,PK 拓宽为(object, tenant_id, field, scope);遗留三列表首次使用时就地迁移,旧计数器并入scope=''field.zod.ts— 文档 /.describe()说明三类 token 与 scope/重置语义测试
autonumber-format.test.ts— 11 例(含时区咬合:上海 vs UTC)sql-driver-autonumber-tokens.test.ts(新)7 例 + 原有 autonumber 套件 12 例,含遗留表迁移 + 旧计数器续号engine-autonumber-defer.test.ts补 2 例(fallback 的{field}分组 + 日期段),全套 667 例通过向后兼容:固定前缀格式 scope 为空,既有序列不变;sequences 表迁移把所有遗留行带到
scope=''。changeset:
@objectstack/spec/@objectstack/objectql/@objectstack/driver-sqlminor。